package com.hwtx.form.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.domain.def.FormProperties;
import io.geekidea.boot.system.dto.SysMenuDto;
import io.geekidea.boot.system.entity.SysMenu;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import static com.hwtx.form.util.I18nTools.listFiles;
import static com.hwtx.form.util.JacksonUtil.readJson;
import static com.hwtx.form.util.JacksonUtil.traverseNodes;

@Slf4j
public class AuthTools {
    public static void main(String[] args) {
        AuthTools authTools = new AuthTools();
        Map<String, List<JacksonUtil.TraverseMatchRule>> rules = Maps.newHashMap();
        List<JacksonUtil.TraverseMatchRule> traverse = Lists.newArrayList();
        traverse.add(new JacksonUtil.TraverseMatchRule("buttons", true));
        traverse.add(new JacksonUtil.TraverseMatchRule("label", false));
        rules.put("type", traverse);
        rules.put("bulkActions", Lists.newArrayList());

//        authTools.checkAuth("page/sys", rules, false, "form_17.json");
        authTools.authNode("page/sys", rules, "form_17.json");
//        authTools.synFileAuth2Db("page/sys", rules, "");
    }

    interface InnerCallback extends JacksonUtil.FindNodeCallback {
        default void afterVisit(JsonNode root, Path path) throws IOException {
        }

        default int beforeVisit(Path path, FormProperties localFormInfo) {
            return 1;
        }
    }

    private void visitPage(String dir, Map<String, List<JacksonUtil.TraverseMatchRule>> rules, InnerCallback callback, final String... filterNames) {

        boolean empty = filterNames == null || Stream.of(filterNames).anyMatch(element -> element == null || element.isEmpty());
        try (Stream<Path> stream = listFiles(dir)) {
            stream.filter(path -> {
                boolean pass = true;
                if (!empty) {
                    pass = Arrays.asList(filterNames).contains(path.toFile().getName());
                }
                return pass;
            }).forEach(path -> {
                try {
                    JsonNode root = readJson(FileUtils.readFileToString(path.toFile(), StandardCharsets.UTF_8));
                    assert root != null;
                    int ret = callback.beforeVisit(path, FormUtil.getLocalFormInfo(path.toFile()));
                    if (ret == 0) {
                        return;
                    }
                    rules.forEach((field, properties) -> traverseNodes(root, field, properties, new JacksonUtil.FindNodeCallback() {
                        @Override
                        public void handleObjectNode(JsonNode root, ObjectNode node, JsonNode parent, String nodeName) {
                            callback.handleObjectNode(root, node, parent, nodeName);
                        }

                        @Override
                        public void handleArrayNode(JsonNode root, ArrayNode parent, JsonNode node, int index) {
                            callback.handleArrayNode(root, parent, node, index);
                        }

                        @Override
                        public void handleTextNode(TextNode node, ObjectNode parent, String property) {
                            callback.handleTextNode(node, parent, property);
                        }
                    }));
                    callback.afterVisit(root, path);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void synFileAuth2Db(String dir, Map<String, List<JacksonUtil.TraverseMatchRule>> rules, String... filterNames) {
        JdbcTemplate jdbcTemplate = getJdbc();
        final AtomicLong parentId = new AtomicLong();
        Set<String> unHandle = Sets.newHashSet();
        visitPage(dir, rules, new InnerCallback() {
            File file;

            @Override
            public int beforeVisit(Path path, FormProperties formProperties) {
                String title = I18nTools.getZh(FormUtil.getI18nKey(formProperties.getMeta().getTitle()));
                file = path.toFile();
                try {
                    List<SysMenu> results = jdbcTemplate.query("select id,type from sys_menu where name = ?", (rs, rowNum) -> {
                        SysMenu sysMenu = new SysMenu();
                        sysMenu.setId(rs.getLong("id"));
                        sysMenu.setType(rs.getInt("type"));
                        return sysMenu;
                    }, title);
                    if (CollectionUtils.isEmpty(results)) {
                        return 0;
                    }
                    SysMenu sysMenu = results.getFirst();
                    if (sysMenu.getType() != 2) {
                        return 0;
                    }
                    parentId.set(sysMenu.getId());
                    return 1;
                } catch (EmptyResultDataAccessException ignored) {

                }
                return 0;
            }

            @Override
            public void handleObjectNode(JsonNode root, ObjectNode node, JsonNode parent, String nodeName) {
                if (node.has("id") && node.get("id").asText().startsWith("auth:")) {
                    String name = getLabel(node);
                    if (name != null) {
                        SysMenuDto sysMenuDto = new SysMenuDto();
                        sysMenuDto.setName(name);
                        sysMenuDto.setParentId(parentId.get());
                        sysMenuDto.setCode(node.get("id").asText());
                        boolean ret = insertAuthMenu(sysMenuDto, jdbcTemplate);
                        if (!ret) {
                            unHandle.add(file.getName());
                            System.out.println(node);
                        }
                    } else {
                        unHandle.add(file.getName());
                        System.out.println("-------" + node);
                    }
                }
            }

            @Override
            public void handleArrayNode(JsonNode root, ArrayNode parent, JsonNode node, int index) {
                handleObjectNode(root, (ObjectNode) node, parent, null);
            }
        });
        log.info("未处理的文件：{}", unHandle);
    }

    public void checkAuth(String dir, Map<String, List<JacksonUtil.TraverseMatchRule>> rules, boolean print, String... filterNames) {
        visitPage(dir, rules, new InnerCallback() {
            final AtomicBoolean hasAuth = new AtomicBoolean(true);

            @Override
            public void handleArrayNode(JsonNode root, ArrayNode parent, JsonNode node, int index) {
                if (!node.has("id")) {
                    if (print) {
                        System.out.println(node.toPrettyString());
                    }
                    hasAuth.set(false);
                }
            }

            @Override
            public void handleObjectNode(JsonNode root, ObjectNode node, JsonNode parent, String nodeName) {
                if (!node.has("id")) {
                    if (!(Objects.equals(node.get("type").asText(), "button")) || node.has("actionType") && Objects.equals(node.get("actionType").asText(), "close")) {
                        return;
                    }
                    if (print) {
                        System.out.println(node.toPrettyString());
                    }
                    hasAuth.set(false);
                } else {
                    String id = node.get("id").asText();
                    if (!id.startsWith("auth:") && (
                            Objects.equals(node.get("type").asText(), "button")
                                    || Objects.equals(node.get("type").asText(), "crud"))) {
                        System.out.println(id);
                    }
                }
            }

            @Override
            public void afterVisit(JsonNode root, Path path) {
                if (!hasAuth.get()) {
                    System.out.println(path.toFile().getName());
                }
            }
        }, filterNames);
    }

    public void authNode(String dir, Map<String, List<JacksonUtil.TraverseMatchRule>> rules, String... filterNames) {
        visitPage(dir, rules, new InnerCallback() {
            FormProperties formProperties = null;
            File file = null;

            @Override
            public int beforeVisit(Path path, FormProperties localFormInfo) {
                formProperties = localFormInfo;
                file = path.toFile();
                return 1;
            }

            @Override
            public void handleArrayNode(JsonNode root, ArrayNode parent, JsonNode node, int index) {
                if (!node.has("id") && node instanceof ObjectNode on) {
                    assert formProperties != null;
                    setLabel(on, formProperties.getType());
                } else {
                    if (!node.get("id").asText().startsWith("auth:")) {
                        System.out.println(node.get("id"));
                    }
                }
            }

            @Override
            public void handleObjectNode(JsonNode root, ObjectNode node, JsonNode parent, String nodeName) {
                if (!node.has("id")) {
                    if ((Objects.equals(node.get("type").asText(), "button")
                            || Objects.equals(node.get("type").asText(), "crud"))) {
                        assert formProperties != null;
                        if (node.has("actionType") && Objects.equals(node.get("actionType").asText(), "close")) {
                            return;
                        }
                        setLabel(node, formProperties.getType());
                    }
                } else {
                    if (!node.get("id").asText().startsWith("auth:")) {
                        if (Objects.equals(node.get("type").asText(), "crud")) {
                            setCrudId(node, formProperties.getType());
                            List<JsonNode> parents = root.findParents("reload");
                            parents.forEach(pp -> {
                                if (pp instanceof ObjectNode on) {
                                    on.set("reload", getCrudId(formProperties.getType()));
                                }
                            });
                            parents = root.findParents("componentId");
                            parents.forEach(pp -> {
                                if (pp instanceof ObjectNode on) {
                                    on.set("componentId", getCrudId(formProperties.getType()));
                                }
                            });
                        }
                    }
                }
            }

            @Override
            public void afterVisit(JsonNode root, Path path) throws IOException {
                out(root, path.toFile().getName());
            }
        }, filterNames);
    }

    private void setLabel(ObjectNode node, String type) {
        if (!node.has("label")) {
            return;
        }
        String label = node.get("label").asText();
        String key = FormUtil.getI18nKey(label);
        if (key == null) {
            return;
        }
        key = key.replaceAll("\\.", ":");
        node.set("id", new TextNode("auth:" + type + ":" + key));
    }

    private void setCrudId(ObjectNode node, String type) {
        node.set("id", new TextNode("auth:" + type + ":crud"));
    }

    private TextNode getCrudId(String type) {
        return new TextNode("auth:" + type + ":crud");
    }

    private String getLabel(ObjectNode node) {
        if (!node.has("label")) {
            throw new RuntimeException("label not found," + node.toPrettyString());
        }
        String label = node.get("label").asText();
        String key = FormUtil.getI18nKey(label);
        if (key == null) {
            return null;
        }
        return I18nTools.getZh(key);

    }

    private void out(JsonNode rootNode, String fileName) throws IOException {
        File out = new File(FormUtil.getTmpPageDir(), fileName);
        FileUtils.writeStringToFile(out, rootNode.toPrettyString(), StandardCharsets.UTF_8);
    }

    private JdbcTemplate getJdbc() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring_boot_plus");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return new JdbcTemplate(dataSource);
    }

    private static boolean insertAuthMenu(SysMenuDto sysMenuDto, JdbcTemplate jdbcTemplate) {
        String sql = "SELECT COUNT(*) FROM sys_menu WHERE code = '" + sysMenuDto.getCode() + "'";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        if (count == null) {
            return false;
        }
        if (count > 0) {
            return false;
        }
        sql = "insert into sys_menu (name, parent_id, type, code, create_id, create_time, update_id, update_time) values (?,?,3,?,?,?,?,?);";
        jdbcTemplate.update(sql, sysMenuDto.getName(), sysMenuDto.getParentId(), sysMenuDto.getCode(),
                "admin", new Timestamp(System.currentTimeMillis()), "admin", new Timestamp(System.currentTimeMillis()));
        return true;
    }
}
